// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QtCore/qcoreapplication.h>
#include <QtCore/private/qwasmsuspendresumecontrol_p.h>
#include <QtCore/qdebug.h>
#include <qtwasmtestlib.h>

using namespace emscripten;

const std::chrono::milliseconds timerTimeout{10};

// Test QWasmSuspendResumeControl suspend/resume and event processing,
// via QWasmTimer native timer events.
class WasmSuspendResumeControlTest: public QObject
{
    Q_OBJECT
private slots:
    void timer();
    void multipleTimers();
    void reuseTimer();
    void cancelTimer();
    void deleteTimer();
    void suspendExclusive();
};

// Verify that a single timer fires
void WasmSuspendResumeControlTest::timer()
{
    QWasmSuspendResumeControl suspendResume;
    bool timerFired = false;

    QWasmTimer timer(&suspendResume, [&timerFired](){
        timerFired = true;
    });
    timer.setTimeout(timerTimeout);

    while (!timerFired) {
        suspendResume.suspend();
        suspendResume.sendPendingEvents();
    }

    QWASMSUCCESS();
}

// Verify that multiple parallel timers fire
void WasmSuspendResumeControlTest::multipleTimers()
{
    QWasmSuspendResumeControl suspendResume;
    const int expectedTimers = 10;
    int compledtedTimers = 0;

    std::unique_ptr<QWasmTimer> timers[expectedTimers];
    for (int i = 0; i < expectedTimers; ++i) {
        timers[i] = std::make_unique<QWasmTimer>(&suspendResume, [&compledtedTimers](){
            ++compledtedTimers;
        });
        timers[i]->setTimeout(timerTimeout * i);
    }

    while (compledtedTimers < expectedTimers) {
        suspendResume.suspend();
        suspendResume.sendPendingEvents();
    }

    QWASMSUCCESS();
}

// Verify that a reused timer fires again
void WasmSuspendResumeControlTest::reuseTimer()
{
    QWasmSuspendResumeControl suspendResume;
    const int expectedTimers = 10;
    int compledtedTimers = 0;

    QWasmTimer timer(&suspendResume, [&compledtedTimers](){
        ++compledtedTimers;
    });

    while (compledtedTimers < expectedTimers) {
        timer.setTimeout(timerTimeout);
        suspendResume.suspend();
        suspendResume.sendPendingEvents();
    }

    QWASMSUCCESS();
}

// Verify that a cancelled timer does not fire
void WasmSuspendResumeControlTest::cancelTimer()
{
    QWasmSuspendResumeControl suspendResume;

    // controlTimer checks that the cancelled testTimer didn't fire
    bool controlFired = false;
    QWasmTimer controlTimer(&suspendResume, [&controlFired](){
        controlFired = true;
    });

    QWasmTimer testTimer(&suspendResume, [](){
        QWASMFAIL("Cancelled timer did fire");
    });

    controlTimer.setTimeout(timerTimeout * 4);
    testTimer.setTimeout(timerTimeout);
    testTimer.clearTimeout();

    while (!controlFired) {
        suspendResume.suspend();
        suspendResume.sendPendingEvents();
    }

    QWASMSUCCESS();
}

// Verify that a deleted timer does not fire
void WasmSuspendResumeControlTest::deleteTimer()
{
    QWasmSuspendResumeControl suspendResume;

    // controlTimer checks that the deleted testTimer didn't fire
    bool controlFired = false;
    QWasmTimer controlTimer(&suspendResume, [&controlFired](){
        controlFired = true;
    });
    controlTimer.setTimeout(timerTimeout * 4);

    {
        QWasmTimer testTimer(&suspendResume, [](){
            QWASMFAIL("Deleted timer did fire");
        });
        testTimer.setTimeout(timerTimeout);
    }

    while (!controlFired) {
        suspendResume.suspend();
        suspendResume.sendPendingEvents();
    }

    QWASMSUCCESS();
}

// Verify that an exclusive suspend resumes for the exclusive event only
void WasmSuspendResumeControlTest::suspendExclusive()
{
    QWasmSuspendResumeControl suspendResume;

    // (re) implement a native timer - this gives us a unique event handler
    // index which we can suspend exclusively on.
    bool exclusiveTimerFired = false;
    auto exclusiveTimerHandler = [&exclusiveTimerFired](emscripten::val) {
        exclusiveTimerFired = true;
    };
    uint32_t exlusiveTimerHandlerIndex = suspendResume.registerEventHandler(std::move(exclusiveTimerHandler));

    std::chrono::milliseconds exclusiveTimerTimeout = timerTimeout * 4;
    double timoutValue = static_cast<double>(exclusiveTimerTimeout.count());
    val jsHandler = suspendResume.jsEventHandlerAt(exlusiveTimerHandlerIndex);
    val::global("window").call<double>("setTimeout", jsHandler, timoutValue);

    // Schedule suppressedTimer to fire before the exclusive timer. Expected
    // behavior is that it doesn't.
    bool suppressedTimerFired = false;
    QWasmTimer suppressedTimer(&suspendResume, [&suppressedTimerFired](){
        suppressedTimerFired = true;
    });
    suppressedTimer.setTimeout(timerTimeout);

    // Suspend exclusively for the exclusive timer, and verify that
    // the correct timers fired.
    suspendResume.suspendExclusive(exlusiveTimerHandlerIndex);
    suspendResume.sendPendingEvents(); // <- also clears exclusive mode
    if (!exclusiveTimerFired)
        QWASMFAIL("Exclusive timer did not fire");
    if (suppressedTimerFired)
        QWASMFAIL("Suppressed timer did fire");

    // Send (all) events, this should give is the suppressed timer
    suspendResume.sendPendingEvents();
    if (!suppressedTimerFired)
        QWASMFAIL("Suppressed timer did not fire");

    QWASMSUCCESS();
}

int main(int argc, char **argv)
{
    auto testObject = std::make_shared<WasmSuspendResumeControlTest>();
    QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject);
    return 0;
}

#include "main.moc"
